package com.zh.demo04.config;

import com.alibaba.fastjson.JSON;
import com.zh.demo04.login.AdminUserDetailsService;
import com.zh.demo04.login.WebUserDetailsService;
import com.zh.demo04.verifycode.sms.SmsCodeService;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import java.io.IOException;
import java.util.Map;

/**
 * Spring Security 配置类
 * 使用默认的配置: formLogin
 *
 * @author ZH
 * @date 23:41 2021/1/7
 */
@Slf4j
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    /**
     * 管理员认证配置（优先级高）
     */
    @Configuration
    @Order(1)
    public static class AdminSecurityConfig {
        @Autowired
        private PasswordEncoder passwordEncoder;

        @Bean
        public SecurityFilterChain adminUserFilterChain(HttpSecurity http) throws Exception {
            //加载不需要权限认证的uri
            String[] permitURIs = new String[]{"/admin/open/**"};

            http
                    .securityMatcher("/admin/**", "/login.html", "/login","/default-ui.css")
                    .authorizeHttpRequests(expression -> {
                            // 放行要公开的接口
                            expression.requestMatchers(permitURIs).permitAll();

                            expression.anyRequest().authenticated();
                    })
                    .userDetailsService(adminUserDetailsService(passwordEncoder))
                    .formLogin(Customizer.withDefaults()) //表单登录
                    .logout(Customizer.withDefaults())
                    .csrf(csrf -> csrf.disable());
            return http.build();
        }

        /**
         * 管理员账号配置
         */
        public AdminUserDetailsService adminUserDetailsService(PasswordEncoder passwordEncoder) {
            return new AdminUserDetailsService(passwordEncoder);
        }
    }

    /**
     * 普通用户认证配置（优先级低）
     */
    @Configuration
    @Order(2)
    public static class AppSecurityConfig {
        @Autowired
        private AuthenticationSuccessHandler authenticationSuccessHandler;
        @Autowired
        private AuthenticationFailureHandler authenticationFailureHandler;
        @Autowired
        private LogoutSuccessHandler logoutSuccessHandler;
        @Autowired
        private AccessDeniedHandler accessDeniedHandler;
        @Autowired
        private AuthenticationEntryPoint myAuthenticationEntryPoint;
        @Autowired
        private PasswordEncoder passwordEncoder;
        @Autowired
        private SmsCodeService smsCodeService;

        @Bean
        public SecurityFilterChain appUserFilterChain(HttpSecurity http) throws Exception {
            //加载不需要权限认证的uri
            String[] permitURIs = new String[]{"/api/open/**"};


            var phoneLoginAuthFilter = new PhoneLoginAuthFilter(smsCodeService, webUserDetailsService(passwordEncoder), authenticationFailureHandler,authenticationSuccessHandler);

            http
                    .securityMatcher("/api/**")
                    //添加自定义的filter
                    .addFilterBefore(phoneLoginAuthFilter, UsernamePasswordAuthenticationFilter.class)

                    .authorizeHttpRequests(expression -> {
                        // 放行要公开的接口
                        expression.requestMatchers(permitURIs).permitAll();

                        expression.anyRequest().authenticated();
                    })
                    .userDetailsService(webUserDetailsService(passwordEncoder))
                    .formLogin(form -> {
                        form.loginProcessingUrl("/api/login");
                        form.successHandler(authenticationSuccessHandler);
                        form.failureHandler(authenticationFailureHandler);
                    })
                    .logout(logout -> {
                        logout.logoutUrl("/api/logout");
                        logout.logoutSuccessHandler(logoutSuccessHandler);
                    })
                    .exceptionHandling(exception -> {
                        exception.accessDeniedHandler(accessDeniedHandler);
                        exception.authenticationEntryPoint(myAuthenticationEntryPoint);
                    })
                    .csrf(csrf -> csrf.disable());
            return http.build();
        }

        /**
         * 普通用户账号配置
         *
         * @return
         */
        public WebUserDetailsService webUserDetailsService(PasswordEncoder passwordEncoder) {
            return new WebUserDetailsService(passwordEncoder);
        }

    }

    //==========================================================
    // 定义所需的Bean
    //==========================================================

    /**
     * 退出登录成功后的处理
     *
     * @return LogoutSuccessHandler
     */
    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return (req, resp, auth) -> {
            log.info("退出登录成功处理Handler....");

            resp.setStatus(HttpStatus.OK.value());
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write("{\"msg\":\"您已退出登录\"}");
        };
    }

    /**
     * 登录认证失败后的处理
     *
     * @return AuthenticationFailureHandler
     */
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (HttpServletRequest req, HttpServletResponse resp, AuthenticationException ex) -> {
            log.info("登录失败处理Handler.... " + ex.getMessage());

            resp.setStatus(HttpStatus.OK.value());
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write(JSON.toJSONString(Map.of("message", "登录失败 -> " + ex.getMessage())));
        };
    }

    /**
     * 登录认证成功后的处理
     *
     * @return AuthenticationSuccessHandler
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (HttpServletRequest req, HttpServletResponse resp, Authentication auth) -> {
            log.info("登录成功处理Handler.... ");

            Object principal = auth.getPrincipal();

            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write(JSON.toJSONString(principal));
        };
    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse resp, AccessDeniedException exception) throws IOException, ServletException {
                //将用户信息响应到前端
                resp.setContentType("application/json;charset=UTF-8");
                resp.setStatus(HttpStatus.FORBIDDEN.value());
                try {
                    resp.getWriter().write(JSON.toJSONString(Map.of("message", exception.getMessage())));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        };
    }

    @Bean
    public AuthenticationEntryPoint myAuthenticationEntryPoint() {
        return new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse resp, AuthenticationException exception) throws IOException, ServletException {
                //将用户信息响应到前端
                resp.setContentType("application/json;charset=UTF-8");
                resp.setStatus(HttpStatus.UNAUTHORIZED.value());
                resp.getWriter().write(JSON.toJSONString(Map.of("message", exception.getMessage())));
            }
        };
    }

    /**
     * 密码加密组件
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 验证码生成业务逻辑
     */
    @Bean
    public SmsCodeService smsCodeService() {
        return new SmsCodeService(4, 5 * 60);
    }

}
